#include "dokerprocessor.h"
#include "dokermodel.h"
#include "objects/pageobject.h"
#include "objects/textobject.h"

#include <QtCore/qdebug.h>
#include <QtCore/qstringlist.h>
#include <QtScript/qscriptvalueiterator.h>
#include <QtScript/qscriptcontextinfo.h>

/*
QScriptValue head(QScriptContext *context, QScriptEngine *engine)
{
    if (context->argumentCount() > 0)
    {
        QScriptValue arg0 = context->argument(0);
        qDebug() << "head():" << arg0.toString().trimmed();
    }
    return QScriptValue();
}
*/


QScriptValue include(QScriptContext *context, QScriptEngine *engine)
{
    QScriptValue result;
    if (context->argumentCount() > 0)
    {
        QScriptValue arg0 = context->argument(0);
        result = engine->undefinedValue();

        QScriptContext *parentContext = context->parentContext();
        context->setActivationObject(parentContext->activationObject());
        context->setThisObject(parentContext->thisObject());
        DokerProcessor *processor = qobject_cast<DokerProcessor*>(engine->parent());
        if (processor)
            result = processor->runScript(arg0.toString().trimmed());
    }
    return result;
}
/*
QScriptValue page_width(QScriptContext *context, QScriptEngine *engine)
{
    qDebug() << "page_width()";
    return QScriptValue();
}





QScriptValue text(QScriptContext *context, QScriptEngine *engine)
{
    QScriptContext *parentContext = context->parentContext();
    context->setActivationObject(parentContext->activationObject());
    context->setThisObject(parentContext->thisObject());
    if (context->argumentCount() > 0)
    {
        QScriptValue arg0 = context->argument(0);
        qDebug() << "text():" << arg0.toString().trimmed();
    }
    QScriptValue result;
    DokerProcessor *processor = qobject_cast<DokerProcessor*>(engine->parent());
    if (processor)
        result = processor->runFunction("text_default");
    return QScriptValue();
}
*/

QScriptValue page(QScriptContext *context, QScriptEngine *engine)
{
    DokerProcessor *processor = qobject_cast<DokerProcessor*>(engine->parent());
    if (!processor)
        return QScriptValue();
    QString style = context->argumentCount() ? context->argument(0).toString().trimmed() : "default";
    QScriptValueList args;
    for (int i = 1; i < context->argumentCount(); ++i)
        args << context->argument(i);
    qDebug() << "page()" << style;
    PageObject *p = processor->addPage(style, args);
    return processor->engine()->newQObject(p);
}

QScriptValue prohibited(QScriptContext *context, QScriptEngine *engine)
{
    DokerProcessor *processor = qobject_cast<DokerProcessor*>(engine->parent());
    if (!processor)
        return QScriptValue();
    const QString msg = "Calling this function is prohibited within current context";
    QScriptContextInfo info(context->parentContext());
    if (!info.fileName().isEmpty() && info.lineNumber() >= 0)
        qCritical() << info.fileName() << ":" << info.lineNumber() << ":"
                    << "Error:" << msg;
    else
        qCritical() << "Error:" << msg;
    return QScriptValue();
}

QScriptValue text(QScriptContext *context, QScriptEngine *engine)
{
    DokerProcessor *processor = qobject_cast<DokerProcessor*>(engine->parent());
    if (!processor)
        return QScriptValue();
    QString style;
    QString content;
    if (context->argumentCount() > 1)
    {
        style = context->argument(0).toString().trimmed();
        content = context->argument(1).toString();
    }
    else
    {
        style = "default";
        if (context->argumentCount())
            content = context->argument(0).toString();
    }

    QScriptValueList args;
    for (int i = 2; i < context->argumentCount(); ++i)
        args << context->argument(i);
    qDebug() << "text()" << style;
    TextObject *t = processor->addText(style, content, args);
    return processor->engine()->newQObject(t);
}

QScriptValue units(QScriptContext *context, QScriptEngine *engine)
{
    if (context->argumentCount() > 0)
    {
        QScriptValue arg0 = context->argument(0);
        qDebug() << "units():" << arg0.toString().trimmed();
    }
    return QScriptValue();
}

DokerProcessor::DokerProcessor(DokerModel *model, QObject *parent) :
    QThread(parent), m_engine(this)
{
    m_model = model;
    m_contextPage = 0;
    restoreGlobalFunctions();
}

DokerProcessor::~DokerProcessor()
{
    clear();
}

PageObject *DokerProcessor::addPage(const QString &style, const QScriptValueList &arguments)
{
    QScriptContext *context = m_engine.currentContext();
    QScriptContext *parentContext = context->parentContext();
    context->setActivationObject(parentContext->activationObject());

    PageObject *p = new PageObject;
    QScriptContextInfo info(parentContext);
    p->linkToSource(info.fileName(), info.lineNumber());
    m_pages.append(p);
    m_contextPage = p;
    QScriptValue pageObj = m_engine.newQObject(p);
    m_engine.currentContext()->activationObject().setProperty("_page", pageObj);
    setGlobalFunction(::prohibited, "page");
    const QString functionName = "page_" + style;
    if (style != "default" || hasFunction(functionName))
        runFunction(functionName, arguments);
    restoreGlobalFunctions();
    emit pageAdded(p);
    m_contextPage = 0;
    return p;
}

TextObject *DokerProcessor::addText(const QString &style, const QString &content,
                                    const QScriptValueList &arguments)
{
    QScriptContext *context = m_engine.currentContext();
    QScriptContext *parentContext = context->parentContext();
    context->setActivationObject(parentContext->activationObject());

    TextObject *t = new TextObject(content);
    QScriptContextInfo info(parentContext);
    t->linkToSource(info.fileName(), info.lineNumber());
    if (m_contextPage)
        m_contextPage->addText(t);
    else
        m_texts.append(t);
    QScriptValue textObj = m_engine.newQObject(t);
    m_engine.currentContext()->activationObject().setProperty("_text", textObj);
    setGlobalFunction(::prohibited, "text");
    const QString functionName = "text_" + style;
    if (style != "default" || hasFunction(functionName))
        runFunction(functionName, arguments);
    restoreGlobalFunctions();
    if (!m_contextPage)
        emit textAdded(t);
    return t;
}

void DokerProcessor::clear()
{
    qDeleteAll(m_pages);
    m_pages.clear();
    PageObject::reset();
    qDeleteAll(m_texts);
    m_texts.clear();
}

bool DokerProcessor::hasFunction(const QString &functionName)
{
    QScriptValue scriptValueFunction = m_engine.currentContext()->activationObject().property(functionName);

    return scriptValueFunction.isFunction();
}

void DokerProcessor::restoreGlobalFunctions()
{
    setGlobalFunction(::page, "page");
    setGlobalFunction(::include, "include");
    setGlobalFunction(::text, "text");
    setGlobalFunction(::units, "units");
}

void DokerProcessor::run()
{
    clear();
    m_engine.pushContext();
    runScript("main.doks");
    m_engine.popContext();
}

QScriptValue DokerProcessor::runFunction(const QString &functionName, const QScriptValueList &arguments)
{
    QScriptValue result;
    QScriptValue scriptValueFunction = m_engine.currentContext()->activationObject().property(functionName);

    if (scriptValueFunction.isFunction())
    {
        result = scriptValueFunction.call(QScriptValue(), arguments);
        if (m_engine.hasUncaughtException())
        {
            QScriptValue exception = m_engine.uncaughtException();
            qCritical() << exception.property("fileName").toString() << ":"
                        << exception.property("lineNumber").toNumber() << ":"
                        << "Error:" << exception.property("message").toString();
            m_engine.clearExceptions();
        }
    }
    else
    {
        const QString msg = "Can't find function: " + functionName;
        QScriptContextInfo info(m_engine.currentContext()->parentContext());
        if (!info.fileName().isEmpty() && info.lineNumber() >= 0)
            qCritical() << info.fileName() << ":" << info.lineNumber() << ":"
                        << "Error:" << msg;
        else
            qCritical() << "Error:" << msg;
    }
    return result;
}

QScriptValue DokerProcessor::runScript(const QString &fileName)
{
    QScriptValue result;
    if (m_model->contains(fileName))
    {
        result = m_engine.evaluate(m_model->fileData(fileName), fileName);
        if (m_engine.hasUncaughtException())
        {
            QScriptValue exception = m_engine.uncaughtException();
            qCritical() << exception.property("fileName").toString() << ":"
                        << exception.property("lineNumber").toNumber() << ":"
                        << "Error:" << exception.property("message").toString();
            m_engine.clearExceptions();
        }
    }
    else
        qCritical() << "Error: Document archive doesn't contain script named" << fileName;
    return result;
}

void DokerProcessor::setGlobalFunction(QScriptEngine::FunctionSignature signature,
                                       const QString &functionName)
{
    QScriptValue newFunctionValue = m_engine.newFunction(signature);
    m_engine.globalObject().setProperty(functionName, newFunctionValue,
                                        QScriptValue::Undeletable | QScriptValue::ReadOnly);
}
